package xyz.lwm.lazycat.utility.reflect;

import org.jetbrains.annotations.NotNull;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * annotation utility
 *
 * @author lwm
 */
public abstract class AnnotationUtil {

    private static final Set<Class<? extends Annotation>> META_ANNOTATIONS = Stream.of(
            Target.class, 
            Retention.class, 
            Inherited.class, 
            Documented.class, 
            SuppressWarnings.class, 
            Override.class, 
            Deprecated.class
    ).collect(Collectors.toSet());

    /**
     * 判断是否基础注解
     *
     * @param annotationType annotation type
     * @return true if is meta annotation
     */
    public static boolean isMetaAnnotation(@NotNull Class<? extends Annotation> annotationType) {
        return META_ANNOTATIONS.contains(annotationType);
    }

    /**
     * <p>
     * 此方法和 {@link java.lang.reflect.AnnotatedElement#getAnnotations()} 的运行逻辑不同,
     * 此方法会返回所有注解，包括注解上的注解，父类的注解, 同时会进行 alias to 操作.
     * </p>
     * <p>
     * 不可缓存 annotations 对象, 因为允许使用 {@link Annotations#addAnnotations(java.lang.reflect.AnnotatedElement)}
     * 添加注解, 可能导致前后不一致
     * </p>
     *
     * @param element element
     * @return annotations
     */
    public static Annotations getAnnotations(@NotNull AnnotatedElement element) {
        if (element instanceof Class) {
            LinkedList<Class<?>> classes = new LinkedList<>();
            for (Class<?> clazz = (Class<?>) element; clazz != null && clazz != Object.class; clazz = clazz.getSuperclass()) {
                // 调整顺序, 这样在 new Annotations 使得子类的注解会覆盖父类的.
                // 先添加接口, 接口属于父类, 所以接口的注解应该被当前类覆盖
                classes.addAll(Arrays.asList(clazz.getInterfaces()));
                classes.addFirst(clazz);
            }
            return new AnnotationsImpl(classes.toArray(new AnnotatedElement[0]));
        } else {
            return new AnnotationsImpl(element);
        }
    }

}
